package com.heo.server;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.log4j.Logger;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.heo.pojo.ControlInfoEntity;
import com.heo.pub.DictEnum;
import com.heo.pub.ServerConfig;
import com.heo.util.RedisUtil;

import io.netty.channel.Channel;

/**
 * Created by zhangcf 本类是用来存放所有的socket连接，只有登录成功的，才会在这个集合中。
 */
public class SocketChannelMap
{
  private static Logger log = Logger.getLogger(SocketChannelMap.class);
  /**
   * 结构 [DEVICE_IEMI]deviceIMEI , Channel
   */
  private static Map<String, Channel> socketMap = new ConcurrentHashMap<String, Channel>();// 线程安全对象，记录当前每个socket连接对应的通道信息

  private static final String DEVICE_IEMI = "[DEVICE_IEMI]";// 主要作用是防止保存的redis里的信息和其他的系统产生冲突

  private static final String CALLBACK_INFO = "[CALLBACK_INFO]";// 控制设备时，发过来的回调参数

  public static void add(String deviceIMEI, int deviceChannelNum, short deviceType, Channel socketChannel)
  {
    try
    {
      socketMap.put(DEVICE_IEMI + deviceIMEI, socketChannel);

      ControlInfoEntity controlInfo = new ControlInfoEntity();
      controlInfo.setControlUrl(ServerConfig.CONTROL_URL);
      controlInfo.setDeviceIMEI(deviceIMEI);
      controlInfo.setDeviceType(deviceType);
      controlInfo.setSocketChannelId(socketChannel.id().asShortText());

      for (int i = 0; i < deviceChannelNum; i++)
      {
        controlInfo.addChannelInfo(i, DictEnum.DeviceChannelStatusCode.FREE);
      }

      // 把设备的信息放到redis里
      String redisJson = JSON.toJSONString(controlInfo);
      RedisUtil.addValue(DEVICE_IEMI + deviceIMEI, redisJson, ServerConfig.READ_IDLE_TIME);
      log.debug("写入redis信息：" + redisJson);
    }
    catch (Exception e)
    {
      log.error("SocketChannelMap.add()出错，参数信息如下：");
      log.error("socketMap.size()=" + socketMap.size());
      log.error(e.getMessage(), e);
    }
  }

  /**
   * 刷新设备在redis里的缓存时间
   * 
   * @param channelId
   */
  public static void refreshDeviceControlInfoExpire(String deviceIMEI)
  {
    try
    {
      RedisUtil.expire(DEVICE_IEMI + deviceIMEI, ServerConfig.READ_IDLE_TIME);
    }
    catch (Exception e)
    {
      log.error("SocketChannelMap.refreshDeviceControlInfoExpire()出错，参数信息如下：");
      log.error("socketMap.size()=" + socketMap.size());
      log.error(e.getMessage(), e);
    }
  }

  /**
   * 刷新设备的信号强度信息
   * 
   * @param channelId
   */
  public static synchronized void updateDeviceSignal(String deviceIMEI, int signal)
  {
    try
    {
      ControlInfoEntity controlInfo = getDeviceControlInfoByIEMI(deviceIMEI);
      if (controlInfo == null)
      {
        // 不太可能出现这个情况。redis里取不到数据，应该是过期了。而过期的原因，一定是设备没有发消息上来。而没消息上来的话，链路会断开。
        // 链路一旦断开，链路里就没有这个设备的对象信息。因此也没办法创建新的控制信息对象了。
        log.error("严重错误，redis里的设备控制信息丢失！");
        return;
      }
      else
      {
        controlInfo.setSignal(signal);
      }
      // 把设备的信息放到redis里
      RedisUtil.addValue(DEVICE_IEMI + deviceIMEI, JSON.toJSONString(controlInfo), ServerConfig.READ_IDLE_TIME);
    }
    catch (Exception e)
    {
      log.error("SocketChannelMap.refreshDeviceControlInfoExpire()出错，参数信息如下：");
      log.error("socketMap.size()=" + socketMap.size());
      log.error(e.getMessage(), e);
    }
  }

  /**
   * 通过设备ID找链路对象
   * 
   * @param deviceIMEI
   * @param deviceChannelId
   * @return
   */
  public static Channel getSocketChannelByIMEI(String deviceIMEI)
  {
    try
    {
      return socketMap.get(DEVICE_IEMI + deviceIMEI);
    }
    catch (Exception e)
    {
      log.error("SocketChannelMap.getSocketChannelByIMEI()出错，参数信息如下：");
      log.error("socketMap.size()=" + socketMap.size());
      log.error(e.getMessage(), e);
      return null;
    }
  }

  /**
   * 通过设备ID找链路对象ID
   * 
   * @param deviceIMEI
   * @param deviceChannelId
   * @return
   */
  public static String getSocketChannelIdByIMEI(String deviceIMEI)
  {
    try
    {
      Channel channel = socketMap.get(DEVICE_IEMI + deviceIMEI);
      if (channel == null)
      {
        return null;
      }
      else
      {
        return socketMap.get(DEVICE_IEMI + deviceIMEI).id().asShortText();
      }
    }
    catch (Exception e)
    {
      log.error("SocketChannelMap.getSocketChannelByIMEI()出错，参数信息如下：");
      log.error("socketMap.size()=" + socketMap.size());
      log.error(e.getMessage(), e);
      return null;
    }
  }

  // public static void refreshDeviceControlInfo(String deviceIMEI,
  // ControlInfoEntity controlInfo)
  // {
  // try
  // {
  // RedisUtil.addValue(DEVICE_IEMI + deviceIMEI,
  // JSON.toJSONString(controlInfo), ServerConfig.READ_IDLE_TIME);
  // }
  // catch (Exception e)
  // {
  // log.error("SocketChannelMap.refreshDeviceStatus()出错，参数信息如下：");
  // log.error("socketMap.size()=" + socketMap.size());
  // log.error(e.getMessage(), e);
  // }
  // }

  /**
   * 非原子操作，需要同步 刷新设备在redis里的状态
   * 
   * @param deviceIMEI
   * @param channelId
   * @param channelStatus
   */
  public static synchronized void refreshDeviceStatus(String deviceIMEI, Integer deviceChannelId, byte channelStatus)
  {
    ControlInfoEntity controlInfo = getDeviceControlInfoByIEMI(deviceIMEI);
    if (controlInfo == null)
    {
      // 不太可能出现这个情况。redis里取不到数据，应该是过期了。而过期的原因，一定是设备没有发消息上来。而没消息上来的话，链路会断开。
      // 链路一旦断开，链路里就没有这个设备的对象信息。因此也没办法创建新的控制信息对象了。
      log.error("严重错误，redis里的设备控制信息丢失！");
      return;
    }
    else
    {
      controlInfo.setChannelStatus(deviceChannelId, channelStatus);
    }
    // 把设备的信息放到redis里
    RedisUtil.addValue(DEVICE_IEMI + deviceIMEI, JSON.toJSONString(controlInfo), ServerConfig.READ_IDLE_TIME);
  }

  public static ControlInfoEntity getDeviceControlInfoByIEMI(String deviceIMEI)
  {
    try
    {
      Object redisObj = RedisUtil.getValue(DEVICE_IEMI + deviceIMEI);
      if (redisObj == null)
      {
        return null;
      }
      else
      {
        return (ControlInfoEntity) JSON.parseObject(redisObj.toString(), ControlInfoEntity.class);
      }
    }
    catch (Exception e)
    {
      log.error("SocketChannelMap.getDeviceByIEMI()出错，参数信息如下：");
      log.error("socketMap.size()=" + socketMap.size());
      log.error(e.getMessage(), e);
      return null;
    }
  }
  //
  // public static String getDeviceControlJSONByIEMI(String deviceIMEI)
  // {
  // try
  // {
  // ControlInfoEntity entity = getDeviceControlInfoByIEMI(deviceIMEI);
  // if (entity == null)
  // {
  // return null;
  // }
  // else
  // {
  // return JSON.toJSONString(entity);
  // }
  // }
  // catch (Exception e)
  // {
  // log.error("SocketChannelMap.getDeviceByIEMI()出错，参数信息如下：");
  // log.error("socketMap.size()=" + socketMap.size());
  // log.error(e.getMessage(), e);
  // return null;
  // }
  // }

  public static int getSocketSize()
  {
    return socketMap.size();
  }

  /**
   * 删除设备信息
   * 
   * @param deviceIMEI
   * @param deviceChannelNum
   */
  public static void remove(String deviceIMEI)
  {
    try
    {
      // 在redis里删除设备信息，这样其他系统查询时，就会显示设备离线
      socketMap.remove(DEVICE_IEMI + deviceIMEI);

      // 这个判断放后面，万一删除出错，超时时间到达时，redis会自动把key删除。
      RedisUtil.remove(DEVICE_IEMI + deviceIMEI);
    }
    catch (Exception e)
    {
      log.error("SocketChannelMap.remove()出错，参数信息如下：");
      log.error("socketMap.size()=" + socketMap.size());
      log.error(e.getMessage(), e);
    }
  }

  /**
   * 设置callback参数，数据返回时，需要取出来回调
   * 
   * @param deviceIMEI
   * @param deviceChannelId
   * @param callbackPara
   */
  public static void setCallBackPara(String deviceIMEI, Integer deviceChannelId, String callbackPara, long timeout)
  {
    RedisUtil.addValue(CALLBACK_INFO + deviceIMEI + "_" + deviceChannelId, callbackPara, timeout);
  }

  /**
   * 清空callback参数
   * 
   * @param deviceIMEI
   * @param deviceChannelId
   * @param deviceChannelNum
   */
  public static void removeCallBackPara(String deviceIMEI, Integer deviceChannelId, int deviceChannelNum)
  {
    if (deviceChannelId != null)
    {
      RedisUtil.remove(CALLBACK_INFO + deviceIMEI + "_" + deviceChannelId);
    }
    else
    {
      for (int i = 0; i < deviceChannelNum; i++)
      {
        RedisUtil.remove(CALLBACK_INFO + deviceIMEI + "_" + deviceChannelNum);
      }
    }
  }

  /**
   * 获得保存的callback参数
   * 
   * @param deviceIMEI
   * @param deviceChannelId
   * @return
   */
  public static String getCallBackPara(String deviceIMEI, Integer deviceChannelId, short workStatus, String callBackType)
  {
    JSONObject json = new JSONObject();
    switch (callBackType)
    {
      case DictEnum.CallBackType.START_RESULT:
        json.put("callBackType", callBackType);
        json.put("deviceIMEI", deviceIMEI);
        json.put("deviceChannelIndex", deviceChannelId);
        json.put("resultCode", getStartResultCode(workStatus));
        json.put("resultMsg", getStartResultMsg(workStatus));
        Object obj1 = RedisUtil.getValue(CALLBACK_INFO + deviceIMEI + "_" + deviceChannelId);
        if (obj1 != null)
        {
          json.put("callbackPara", obj1.toString());
        }
        return json.toString();

      case DictEnum.CallBackType.STATUS_REPORT:
        json.put("callBackType", callBackType);
        json.put("deviceIMEI", deviceIMEI);
        json.put("deviceChannelIndex", deviceChannelId);
        json.put("resultCode", getDeviceStatusResultCode(workStatus));
        json.put("resultMsg", getDeviceStatusResultMsg(workStatus));
        Object obj2 = RedisUtil.getValue(CALLBACK_INFO + deviceIMEI + "_" + deviceChannelId);
        if (obj2 != null)
        {
          json.put("callbackPara", obj2.toString());
        }
        return json.toString();

      default:
        return null;
    }
  }

  private static String getStartResultCode(short workStatus)
  {
    switch (workStatus)
    {
      case DictEnum.StartResultCode.SUCCESS:
        return "SUCCESS";
      case DictEnum.StartResultCode.FAIL:
        return "FAIL";
      default:
        return "FAIL";
    }
  }

  private static String getStartResultMsg(short workStatus)
  {
    switch (workStatus)
    {
      case DictEnum.StartResultCode.SUCCESS:
        return DictEnum.StartResultMsg.SUCCESS;
      case DictEnum.StartResultCode.FAIL:
        return DictEnum.StartResultMsg.FAIL;
      default:
        return DictEnum.StartResultMsg.FAIL;
    }
  }

  private static String getDeviceStatusResultCode(short workStatus)
  {
    switch (workStatus)
    {
      case DictEnum.DeviceChannelStatusCode.DEVICE_CHANNEL_ERROR:
        return "DEVICE_CHANNEL_ERROR";
      case DictEnum.DeviceChannelStatusCode.DEVICE_ERROR:
        return "DEVICE_ERROR";
      case DictEnum.DeviceChannelStatusCode.FREE:
        return "FREE";
      case DictEnum.DeviceChannelStatusCode.START_SUCCESS:
        return "START_SUCCESS";
      case DictEnum.DeviceChannelStatusCode.WORK_DONE:
        return "WORK_DONE";
      case DictEnum.DeviceChannelStatusCode.WORKING:
        return "WORKING";
      default:
        return "UNKNOW_ERROR";
    }
  }

  private static String getDeviceStatusResultMsg(short workStatus)
  {
    switch (workStatus)
    {
      case DictEnum.DeviceChannelStatusCode.DEVICE_CHANNEL_ERROR:
        return DictEnum.DeviceChannelStatusMsg.DEVICE_CHANNEL_ERROR;
      case DictEnum.DeviceChannelStatusCode.DEVICE_ERROR:
        return DictEnum.DeviceChannelStatusMsg.DEVICE_ERROR;
      case DictEnum.DeviceChannelStatusCode.FREE:
        return DictEnum.DeviceChannelStatusMsg.FREE;
      case DictEnum.DeviceChannelStatusCode.START_SUCCESS:
        return DictEnum.DeviceChannelStatusMsg.START_SUCCESS;
      case DictEnum.DeviceChannelStatusCode.WORK_DONE:
        return DictEnum.DeviceChannelStatusMsg.WORK_DONE;
      case DictEnum.DeviceChannelStatusCode.WORKING:
        return DictEnum.DeviceChannelStatusMsg.WORKING;
      default:
        return DictEnum.DeviceChannelStatusMsg.UNKNOW_ERROR;
    }
  }
}
